#pragma once
#include <string>
#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <unordered_map>
#include <map>
#include "File.h"

static int MAX_UNIFORM_BUFF_SIZE = 64;
static int MAX_ATTRIBUTE_NAME_LENGTH = 256;

extern GLenum glCheckError_(const char* file, int line);
#define glCheckError() glCheckError_(__FILE__, __LINE__) 
extern GLsizei GetGLDataTypeSize(GLenum size);
extern GLsizei GetGLAttributeSize(GLenum size);

struct UniformInfo
{
	int count = 0;
	int location = -1;
	//in opengl, type means uniform data type, i.e. GL_FLOAT_VEC2, while in metal type means data basic type, i.e. float
	unsigned int type = 0;
	bool isArray = false;
	unsigned int size = 0;
	unsigned int bufferOffset = 0;
	bool isDirty = true;
};

struct AttributeBindInfo
{
	std::string attributeName;
	int         location = -1;
	int         size = 0;
	int         type = 0;
	int         num = 0;
};

class Context;
class Shader
{
public:
	Shader(Context* context);
	virtual ~Shader();
	bool BeginLoad(const char* path);
	bool ProcessSource(std::string& code, File& source);
	void CommentOutFunction(std::string& code, const std::string& signature);
	unsigned GetProgramObject();
	void GetActiveAttributes(unsigned programID, std::unordered_map<std::string, AttributeBindInfo>& attributeBindInfo);
	void ComputeUniformInfos(unsigned programID, unsigned& buffsize, std::unordered_map<std::string, UniformInfo>& activeUniformInfos);
	void SetDefines(const std::vector<std::string>& def) { defines_ = def; }
private:
	std::vector<std::string> defines_;
	std::string shaderCode_;
	unsigned programID_{};
	Context* context_{ nullptr };
};

class ProgramState
{
public:
	explicit ProgramState(Context* context);
	~ProgramState();
	void InitWithShader(Shader* shader);

	void SetUniform(const char* name, const void* data);

	void Apply(bool force = true);

	Shader* GetShader() { return shader_; }

	const std::unordered_map<std::string, AttributeBindInfo>& GetAttributeInfo() { return attributeBindInfo_; }
private:
	Shader* shader_{};
	char* uniformBuffer_{};
	unsigned totalBufferSize_;
	std::unordered_map<std::string, AttributeBindInfo> attributeBindInfo_;
	std::unordered_map<std::string, UniformInfo> activeUniformInfos_;
	unsigned programID_;
	Context* context_{ nullptr };
};

